﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MongoDB.Bson;
using System.ComponentModel.DataAnnotations;

namespace MongoServices
{
	/// <summary>
	/// The base &quot;Read/Write&quot; service.
	/// </summary>
	/// <typeparam name="T">The MongoServices model to be used by this service.</typeparam>
	public abstract class ReadWriteService<T> : ReadOnlyService<T>, IReadWriteService<T>
		where T : MongoServicesModel
	{
		#region Protected Properties
		/// <summary>
		/// Gets the <see cref="IReadWriteRepository&lt;T&gt;"/> that was passed into the constructor.
		/// </summary>
		protected IReadWriteRepository<T> ReadWriteRepository { get; private set; }
		#endregion

		#region Public Constructors
		/// <summary>
		/// Constructs a ReadWriteService based on the <c>readWriteRepository</c> parameter.
		/// </summary>
		/// <param name="readWriteRepository">The <see cref="IReadWriteRepository&lt;T&gt;"/> to be used with this service.</param>
		public ReadWriteService(IReadWriteRepository<T> readWriteRepository)
			: base(readWriteRepository)
		{
			ReadWriteRepository = readWriteRepository;
		}
		#endregion

		#region Private Methods
		private bool ProcessBeforeSaveProcesses(T model)
		{
			var allProcessed = true;

			if (ServiceConfig.BeforeSaveProcesses.Any(p => p(model) == false))
			{
				allProcessed = false;
			}

			return allProcessed;
		}

		private void ProcessAfterSaveProcesses(T model)
		{
			ServiceConfig.AfterSaveProcesses.ForEach(a => a(model));
		}

		private bool Validate(T model)
		{
			var results = new List<ValidationResult>();
			Validator.TryValidateObject(model, new ValidationContext(model, null, null), results, true);
			return results.Count == 0;
		}
		#endregion

		#region Public Methods
		/// <summary>
		/// Saves the <see cref="MongoServicesModel"/>.
		/// </summary>
		/// <param name="model">The <see cref="MongoServicesModel"/> to be saved.</param>
		/// <returns>This is the result of the save.</returns>
		public SaveResult Save(T model)
		{
			if (!ProcessBeforeSaveProcesses(model))
				return SaveResult.BeforeSaveFailure;

			if (!Validate(model))
				return SaveResult.ValidationFailure;

			ReadWriteRepository.Save(model);

			ProcessAfterSaveProcesses(model);

			return SaveResult.Saved;
		}

		/// <summary>
		/// Saves a collection of <see cref="MongoServicesModel"/>s.
		/// </summary>
		/// <param name="models">The collection of <see cref="MongoServicesModel"/>s to be saved.</param>
		/// /// <returns>The <see cref="SaveResult"/> for each model.</returns>
		public IDictionary<MongoServicesModel, SaveResult> Save(IEnumerable<T> models)
		{
			var results = new Dictionary<MongoServicesModel, SaveResult>();

			foreach (var model in models)
			{
				results.Add(model, Save(model));
			}

			return results;
		}

		/// <summary>
		/// Deleted one document from the mongoDB collection.
		/// </summary>
		/// <param name="id">The &quot;Id&quot; of the document to be deleted.</param>
		public void DeleteById(ObjectId id)
		{
			ReadWriteRepository.DeleteById(id);
		}
		#endregion

		#region Static Methods
		/// <summary>
		/// Adds a process that will occur before any save that happens through this service.
		/// </summary>
		/// <param name="process">The before save process to add.</param>
		public static void AddBeforeSaveProcess(Func<T, bool> process)
		{
			ServiceConfig.BeforeSaveProcesses.Add(process);
		}

		/// <summary>
		/// Removes all of the before save processes.
		/// </summary>
		public static void ClearBeforeSaveProcesses()
		{
			ServiceConfig.BeforeSaveProcesses.Clear();
		}

		/// <summary>
		/// Adds a process that will occur after any save that happens through this service.
		/// </summary>
		/// <param name="process">The after save process to add.</param>
		public static void AddAfterSaveProcess(Action<T> process)
		{
			ServiceConfig.AfterSaveProcesses.Add(process);
		}

		/// <summary>
		/// Removes all of the after save processes.
		/// </summary>
		public static void ClearAfterSaveProcesses()
		{
			ServiceConfig.AfterSaveProcesses.Clear();
		}
		#endregion
	}
}
